package com.ruoyi.flow.common.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import com.google.common.base.Joiner;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * HttpUtils工具类，用于执行POST或者GET请求. <br>
 * 1)获取HttpUtils实例 <br>
 * 1. 通过new获取：</br>
 * <p>
 * <pre>
 * HttpUtils utils = new HttpUtils(&quot;http://localhost/a.do?username=yyy&quot;);
 * </pre>
 * <p>
 * 或者
 * <p>
 * <pre>
 * HttpUtils utils = HttpUtils.defaults(&quot;http://localhost/a.do?username=yyy&quot;);
 * </pre>
 * <p>
 * 2. 通过builder创建，可以自定义超时时间：</br>
 * <p>
 * <pre>
 * HttpUtils utils = HttpUtils.custom(&quot;http://localhost/a.do?username=yyy&quot;).setConnectionTimeout(100).setReadTimeout(100).create();
 * </pre>
 * <p>
 * 2). 执行GET请求:</br>
 * <p>
 * <pre>
 * HttpUtils utils = new HttpUtils(&quot;http://localhost/a.do?username=yyy&quot;);
 * boolean flag = utils.doGet();
 * if (flag) {
 *     // 将响应作为字符串返回
 *     String result = utils.getResponseAsString();
 *     // or 下载一个文件，将响应内容输出到文件输出流
 *     utils.getResponseAsOutputStream(out);
 * }
 * </pre>
 * <p>
 * <br>
 * 3). 执行POST请求:</br>
 * <p>
 * <pre>
 * HttpUtils utils = new HttpUtils(&quot;http://localhost/a.do&quot;);
 * boolean flag = utils.doPost(jsonString);
 * if (flag) {
 *     // 将响应作为字符串返回
 *     String result = utils.getResponseAsString();
 * }
 * </pre>
 *
 * @author wtian
 * @version 1.2
 * @see HttpURLConnection
 */
public class HttpUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class);

    private static final String EMPTY_STRING = "";

    private static final byte[] EMPTY_BYTE = new byte[0];

    private static final String DEFAULT_ENCODING = "UTF-8";

    private static final int RESP_OK = 200;

    private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded";

    /**
     * 请求URL
     */
    private String url;

    private HttpURLConnection conn;

    private InputStream in;

    private int responseCode;

    private boolean flag = true;

    /**
     * @param url               请求URL地址
     * @param connectionTimeout 连接超时时间
     * @param readTimeout       响应超时时间
     */
    private HttpUtils(String url, int connectionTimeout, int readTimeout, Map<String, String> headers) {
        if (StringUtils.isEmpty(url)) {
            LOGGER.error("null is not a url value.", new IllegalArgumentException("null is not a url value."));
            flag = false;
        } else {
            try {
                this.url = url;
                URL ul = new URL(url);
                HttpURLConnection conn = (HttpURLConnection) ul.openConnection();
                // 设置HTTP头信息
                if (headers != null && !headers.isEmpty()) {
                    for (Map.Entry<String, String> entry : headers.entrySet()) {
                        conn.addRequestProperty(entry.getKey(), entry.getValue());
                    }
                }
                conn.setConnectTimeout(connectionTimeout);
                conn.setReadTimeout(readTimeout);
                conn.setDefaultUseCaches(false);
                conn.setRequestProperty("Accept-Charset", "UTF-8");
                this.conn = conn;
            } catch (MalformedURLException e) {
                LOGGER.error("URL <" + url + "> format exception.", e);
                flag = false;
                destory();
            } catch (IOException e) {
                LOGGER.error("URL <" + url + "> connect error.", e);
                flag = false;
                destory();
            }
        }
    }

    /**
     * 创建HttpUtils实例。
     *
     * @param url 。 如果是get请求，那么该url应该包含参数；如果是post请求，那么该url应该不包含请求参数，请求参数作为
     *            {@code doPost()}的方法参数传入。 </br> <b>该方法已过期</b>，推荐使用
     *            <code>HttpUtils.defaults(url)</code>创建HttpUtils实例。
     */
    private HttpUtils(String url) {
        this(new HttpInvokerBuilder(url));
    }

    private HttpUtils(HttpInvokerBuilder builder) {
        this(builder.url, builder.connectionTimeout, builder.readTimeout, builder.headers);
    }

    /**
     * 基于默认参数创建HttpUtils实例
     *
     * @param url 请求URL
     * @return
     */
    public static HttpUtils defaults(String url) {
        return new HttpInvokerBuilder(url).build();
    }

    /**
     * 构建参数
     *
     * @param url 请求URL
     * @return
     */
    public static HttpInvokerBuilder custom(String url) {
        return new HttpInvokerBuilder(url);
    }

    /**
     * 发送POST请求.
     *
     * @param params 请求参数作为Map类型的键值对传入。
     * @return 如果响应状态码是200，则返回true；否则返回false.
     */
    public boolean doPost(Map<String, String> params) throws IOException, ProtocolException {
        String param = "";
        if (MapUtils.isNotEmpty(params)) {
            param = Joiner.on("&").withKeyValueSeparator("=").join(params);
        }
        return doPost(param);
    }

    /**
     * 发送POST请求.
     *
     * @param params 请求参数作为Map类型的键值对传入。
     * @return 如果响应状态码是200，则返回true；否则返回false.
     * @throws IOException
     * @throws ProtocolException
     */
    public boolean doPostQuietly(Map<String, String> params) {
        String param = null;
        if (MapUtils.isNotEmpty(params)) {
            param = Joiner.on("&").withKeyValueSeparator("=").join(params);
        }
        return doPostQuietly(param);
    }

    /**
     * 发送POST请求。 <br/>
     *
     * @param params 参数字符串
     * @return 如果响应状态码是200，则返回true；否则返回false.
     */
    public boolean doPostQuietly(String params) {
        return doPostQuietly(params, DEFAULT_CONTENT_TYPE);
    }

    /**
     * 发送POST请求，并抛出异常。 <br/>
     * 调用该方法后，如果出现异常，请在异常中手动调用<code>destory()</code>方法关闭流。
     * @param params 参数字符串
     * @return 如果响应状态码是200，则返回true；否则返回false.
     */
    public boolean doPost(String params) throws IOException, ProtocolException {
        return doPost(params, DEFAULT_CONTENT_TYPE);
    }

    /**
     * 执行POST请求，并抛出异常。<br/>
     *
     * @param params      请求参数
     * @param contentType 请求协议类型
     * @return
     * @throws IOException
     * @throws ProtocolException
     */
    public boolean doPost(String params, String contentType) throws IOException {
        if (this.conn != null && flag) {
            OutputStream os = null;
            try {
                this.conn.setDoOutput(true);
                this.conn.setDoInput(true);
                this.conn.setRequestMethod("POST");
                this.conn.setRequestProperty("Content-Type", contentType);
                os = this.conn.getOutputStream();
                if (!StringUtils.isEmpty(params)) {
                    os.write(params.getBytes("UTF-8"));
                }
                os.flush();
                this.in = this.conn.getInputStream();
                this.responseCode = this.conn.getResponseCode();
                return isRequestSuccess();
            } catch (Exception e) {
                e.printStackTrace();
                destory();
            } finally {
                IOUtils.closeQuietly(os);
            }
        }
        return false;
    }

    /**
     * 执行POST请求，并在内部捕获异常
     *
     * @param params      请求参数
     * @param contentType 请求协议类型
     * @return
     */
    public boolean doPostQuietly(String params, String contentType) {
        try {
            return doPost(params, contentType);
        } catch (IOException e) {
            e.printStackTrace();
            destory();
        }
        return false;
    }

    /**
     * 执行GET请求，并抛出异常
     *
     * @return
     * @throws ProtocolException
     * @throws IOException
     */
    public boolean doGetQuietly(){
        try {
            return doGet();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 执行get请求。
     *
     * @return 如果响应状态码是200，则返回true；否则返回false.
     */
    public boolean doGet() throws IOException {
        if (this.conn != null && flag) {
            try {
                this.conn.setRequestMethod("GET");
                this.conn.connect();
                this.in = this.conn.getInputStream();
                this.responseCode = this.conn.getResponseCode();
                return isRequestSuccess();
            } catch (Exception e) {
                LOGGER.error("get url <" + this.url + "> error", e);
                destory();
            }
        }
        return false;
    }

    /**
     * 判断请求的响应状态
     *
     * @param
     * @return
     * @throws IOException
     */
    private boolean isRequestSuccess() throws IOException {
        if (this.responseCode == RESP_OK) {
            return true;
        }
        return false;
    }

    /**
     * 关闭流
     */
    public void destory(){
        if(this.conn!=null){
            IOUtils.close(this.conn);
        }
        IOUtils.closeQuietly(this.in);
    }

    /**
     * 将请求返回的字节流转换为字符串
     *
     * @return
     */
    public String getResponseAsString() {
        if (this.in == null) {
            LOGGER.error("InputStream is empty.");
            return EMPTY_STRING;
        }
        try {
            return IOUtils.toString(this.in, DEFAULT_ENCODING);
        } catch (IOException e) {
            LOGGER.error("convert input to String error.", e);
        } finally {
            destory();
        }
        return EMPTY_STRING;

    }

    /**
     * 将请求返回的字节流转换为byte数组
     *
     * @return
     */
    public byte[] getResponseAsByteArray() {
        if (in == null) {
            return EMPTY_BYTE;
        }
        try {
            return IOUtils.toByteArray(this.in);
        } catch (IOException e) {
            LOGGER.error("convert input to bytearray error.", e);
        } finally {
            destory();
        }
        return EMPTY_BYTE;
    }

    /**
     * 将请求返回的字节流输出到指定的输出流中。 <br>
     * <b>调用方需要关闭输出流</b>
     * @param os
     * @throws IOException
     */
    public void getResponseAsOutputStream(OutputStream os) throws IOException{
        if (in == null) {
            return;
        }
        try {
            IOUtils.copy(in, os);
        } finally {
            destory();
        }

    }

    /**
     * 获取HttpUrlConnection中的输入流
     *
     * @return
     */
    public InputStream getInputStream() {
        return this.in;
    }

    /**
     * 获取响应状态 <br/>
     * 例如，如果请求成功，将返回200.如果没有成功发起HTTP请求，将返回-1.
     *
     * @return
     */
    public int getResponseCode() {
        return this.responseCode;
    }

    public static class HttpInvokerBuilder {
        protected int connectionTimeout = 30000;

        protected int readTimeout = 50000;

        protected Map<String, String> headers = new HashMap<String, String>();

        protected String url;

        public HttpInvokerBuilder(String url) {
            super();
            this.url = url;
        }

        /**
         * 设置连接超时时间
         *
         * @param connectionTimeout
         * @return
         */
        public HttpInvokerBuilder setConnectionTimeout(int connectionTimeout) {
            this.connectionTimeout = connectionTimeout;
            return this;
        }

        /**
         * 设置响应超时时间
         *
         * @param readTimeout
         * @return
         */
        public HttpInvokerBuilder setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public HttpInvokerBuilder addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public HttpInvokerBuilder setHeaders(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public int getConnectionTimeout() {
            return connectionTimeout;
        }

        public int getReadTimeout() {
            return readTimeout;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public String getUrl() {
            return url;
        }

        /**
         * 创建{@code HttpUtils}
         *
         * @return
         */
        public HttpUtils build() {
            return new HttpUtils(this);
        }

        @Override
        public String toString() {
            return new StringBuffer("[URL:").append(url).append(",CONNECTIONTIMEOUT:").append(connectionTimeout).append(",READTIMEOUT:").append(readTimeout).append("]").toString();
        }
    }

}
